<?php

declare(strict_types=1);

namespace app\service\sys;

use app\model\AuthPermission;
use app\model\AuthRole;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class PermissionService
{
    /**
     * 获取权限分页列表，支持关键字、客户端与状态过滤。
     */
    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = AuthPermission::with([
            'roles' => static function ($relation) {
                $relation->field(['id', 'name', 'slug', 'scope_type', 'status']);
            },
        ])->order('id', 'desc');

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('name', '%' . $keyword . '%')
                    ->whereOr('code', 'like', '%' . $keyword . '%')
                    ->whereOr('description', 'like', '%' . $keyword . '%');
            });
        }

        if (!empty($filters['client_type'])) {
            $query->where('client_type', (string) $filters['client_type']);
        }

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    /**
     * 查找单个权限并附带角色快照。
     */
    public function findById(int $id): ?AuthPermission
    {
        return AuthPermission::with([
            'roles' => static function ($relation) {
                $relation->field(['id', 'name', 'slug', 'scope_type', 'status']);
            },
        ])->find($id);
    }

    public function create(array $payload): AuthPermission
    {
        $name = trim((string) ($payload['name'] ?? ''));
        $code = trim((string) ($payload['code'] ?? ''));
        $description = trim((string) ($payload['description'] ?? ''));
        $clientType = (string) ($payload['client_type'] ?? 'admin');
        $status = isset($payload['status']) ? (int) $payload['status'] : 1;
        $roleIds = $this->normalizeIds($payload['role_ids'] ?? []);

        if ($name === '') {
            throw new InvalidArgumentException('权限名称不能为空');
        }

        if ($code === '') {
            throw new InvalidArgumentException('权限编码不能为空');
        }

        $this->assertCodeUnique($code);

        if (!in_array($clientType, ['admin', 'seller', 'buyer', 'desktop', 'mobile'], true)) {
            $clientType = 'admin';
        }

        if (!in_array($status, [0, 1], true)) {
            $status = 1;
        }

        return Db::transaction(function () use (
            $name,
            $code,
            $description,
            $clientType,
            $status,
            $roleIds
        ) {
            /** @var AuthPermission $permission */
            $permission = AuthPermission::create([
                'name' => $name,
                'code' => $code,
                'description' => $description,
                'client_type' => $clientType,
                'status' => $status,
            ]);

            $this->syncRoles($permission, $roleIds);

            return $this->refreshPermission($permission);
        });
    }

    public function update(AuthPermission $permission, array $payload): AuthPermission
    {
        $name = array_key_exists('name', $payload) ? trim((string) $payload['name']) : $permission->name;
        $code = array_key_exists('code', $payload) ? trim((string) $payload['code']) : $permission->code;
        $description = array_key_exists('description', $payload) ? trim((string) $payload['description']) : $permission->description;
        $clientType = array_key_exists('client_type', $payload) ? (string) $payload['client_type'] : $permission->client_type;
        $status = array_key_exists('status', $payload) ? (int) $payload['status'] : (int) $permission->status;
        $roleIds = $this->normalizeIds($payload['role_ids'] ?? null);

        if ($name === '') {
            throw new InvalidArgumentException('权限名称不能为空');
        }

        if ($code === '') {
            throw new InvalidArgumentException('权限编码不能为空');
        }

        if ($code !== $permission->code) {
            $this->assertCodeUnique($code, (int) $permission->id);
        }

        if (!in_array($clientType, ['admin', 'seller', 'buyer', 'desktop', 'mobile'], true)) {
            $clientType = $permission->client_type;
        }

        if (!in_array($status, [0, 1], true)) {
            $status = (int) $permission->status;
        }

        return Db::transaction(function () use (
            $permission,
            $name,
            $code,
            $description,
            $clientType,
            $status,
            $roleIds
        ) {
            $permission->save([
                'name' => $name,
                'code' => $code,
                'description' => $description,
                'client_type' => $clientType,
                'status' => $status,
            ]);

            if ($roleIds !== null) {
                $this->syncRoles($permission, $roleIds);
            }

            return $this->refreshPermission($permission);
        });
    }

    public function delete(AuthPermission $permission): bool
    {
        return (bool) Db::transaction(function () use ($permission) {
            $permission->roles()->detach();
            return $permission->delete();
        });
    }

    public function format(AuthPermission $permission): array
    {
        $roles = $permission->roles instanceof Collection
            ? $permission->roles
            : Collection::make($permission->roles ?? []);

        return [
            'id' => (int) $permission->id,
            'name' => $permission->name,
            'code' => $permission->code,
            'description' => $permission->description,
            'client_type' => $permission->client_type,
            'status' => (int) $permission->status,
            'create_time' => $permission->create_time,
            'update_time' => $permission->update_time,
            'roles' => array_values($roles->map(static function ($role) {
                return [
                    'id' => (int) $role->id,
                    'name' => $role->name,
                    'slug' => $role->slug,
                    'scope_type' => $role->scope_type,
                    'status' => (int) $role->status,
                ];
            })->toArray()),
        ];
    }

    public function formatCollection(iterable $permissions): array
    {
        $results = [];
        foreach ($permissions as $permission) {
            if ($permission instanceof AuthPermission) {
                $results[] = $this->format($permission);
            }
        }

        return $results;
    }

    private function syncRoles(AuthPermission $permission, array $roleIds): void
    {
        $roleIds = array_values(array_unique(array_map('intval', $roleIds)));
        $roleIds = array_filter($roleIds, static fn(int $id) => $id > 0);

        $validIds = $roleIds === []
            ? []
            : array_values(AuthRole::whereIn('id', $roleIds)->column('id'));

        if (count($validIds) !== count($roleIds)) {
            throw new InvalidArgumentException('存在无效的角色ID');
        }

        $relation = $permission->roles();
        $relation->detach();

        if ($validIds !== []) {
            $relation->attach($validIds);
        }
    }

    private function assertCodeUnique(string $code, ?int $excludeId = null): void
    {
        $query = AuthPermission::where('code', $code);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('权限编码已存在');
        }
    }

    private function normalizeIds(mixed $ids): ?array
    {
        if ($ids === null) {
            return null;
        }

        if (!is_array($ids)) {
            throw new InvalidArgumentException('角色参数格式不正确');
        }

        $converted = array_values(array_unique(array_map('intval', $ids)));

        return array_values(array_filter($converted, static fn(int $id) => $id > 0));
    }

    private function refreshPermission(AuthPermission $permission): AuthPermission
    {
        /** @var AuthPermission $fresh */
        $fresh = $this->findById((int) $permission->id);
        return $fresh;
    }
}
